﻿namespace Legend
{
    using System.Collections.Generic;
    using NUnit.Framework;
    using System.Globalization;

    [TestFixture]
    public class MaybeTests
    {
        [Test]
        public void Should_invoke_binder_with_value_from_first_maybe()
        {
            // Arrange
            var wasCalled = false;
            M("foo").Bind(x =>
                {
                    wasCalled = true;
                    Assert.That(x, Is.EqualTo("foo"));
                    return M(x);
                });
            // Act

            // Assert
            Assert.That(wasCalled, Is.True);
        }

        [Test]
        public void Should_not_execute_second_bind_when_first_is_nothing()
        {
            // Arrange
            Maybe<object>.Nothing.Bind(x =>
                {
                    Assert.Fail("Invoked bind of nothing");
                    return Maybe<object>.Just(x);
                });

            // Act

            // Assert

        }

        [Test]
        public void Should_be_able_to_chain_multiple_nothing_monads()
        {
            // Arrange

            // Act

            // Assert
            Maybe<object>.Nothing.Bind(M).Bind(M);
        }

        [Test]
        public void Should_be_able_to_use_maybe_in_linq_expression()
        {
            // Arrange
            var result = from a in M("foo")
                         from b in M(1)
                         select new { a, b };

            // Act

            // Assert
            Assert.That(result.GetValueOrDefault(), Is.EqualTo(new { a = "foo", b = 1 }));
        }

        [Test]
        public void Should_do_action()
        {
            // Arrange
            string passed = null;
            M("foo").Do(x => { passed = x; });

            // Act

            // Assert
            Assert.That(passed, Is.EqualTo("foo"));
        }

        IEnumerable<Maybe<object>> BindingExpressions
        {
            get
            {
                yield return Maybe.NotNull("foo");
                yield return "1".ParseInt();
                yield return "1 kr".ParseInt(NumberStyles.Currency, CultureInfo.GetCultureInfo("sv-SE"));
                yield return new Dictionary<string, int>()
                    {
                        {"foo", 1}
                    }.GetValue("foo");
                yield return M<object>("foo").TryCast<string>();
                yield return "false".ParseBool();
                yield return "true".ParseBool();
                yield return "ja".ToBoolean(x => x.Equals("ja"), x => x.Equals("nej"));
                yield return "nej".ToBoolean(x => x.Equals("ja"), x => x.Equals("nej"));
                yield return M("foo").Where(x => x.Equals("foo"));
            }
        }

        IEnumerable<Maybe<object>> NonBindingExpressions 
        {
            get
            {
                yield return Maybe.NotNull(default(object));
                yield return "non int".ParseInt();
                yield return new Dictionary<string, int>().GetValue("foo");
                yield return M(new object()).TryCast<string>();
                yield return "foo".ParseBool();
                yield return "yup".ToBoolean(x => x.Equals("ja"), x => x.Equals("nej"));
                yield return M("foo").Where(x => x.Equals("bar"));
            }
        }

        [Test]
        public void Should_pass_true_to_binding_when_parsing_truthy_value_invoking_to_boolean()
        {
            // Arrange
            var result = "1".ToBoolean(x => x.Equals("1"), x => x.Equals("2"));

            // Act

            // Assert
            AssertEquals(true, result);
        }

        [Test]
        public void Should_pass_false_to_binding_when_parsing_falsy_value_invoking_to_boolean()
        {
            // Arrange
            var result = "2".ToBoolean(x => x.Equals("1"), x => x.Equals("2"));

            // Act

            // Assert
            AssertEquals(false, result);
        }

        [TestCaseSource("BindingExpressions")]
        public void Should_bind_binding_expressions(Maybe<object> maybe)
        {
            AssertWillBind(maybe);
        }

        [TestCaseSource("NonBindingExpressions")]
        public void Should_not_bind_non_binding_expressions(Maybe<object> maybe)
        {
            AssertWillNotBind(maybe);
        }

        private static Maybe<T> M<T>(T value)
        {
            return Maybe.Return(value);
        }

        private static void AssertWillNotBind<T>(Maybe<T> maybe)
        {
            var wasBound = false;
            maybe.Do(x => wasBound = true);
            Assert.That(wasBound, Is.False);
        }

        private static void AssertWillBind<T>(Maybe<T> maybe)
        {
            var wasBound = false;
            maybe.Do(x => wasBound = true);
            Assert.That(wasBound, Is.True);
        }

        private static void AssertEquals<T>(T expectedValue, Maybe<T> maybe)
        {
            bool wasBound = false;
            maybe.Do(x =>
                {
                    Assert.That(x, Is.EqualTo(expectedValue));
                    wasBound = true;
                });
            Assert.That(wasBound, Is.True, "The maybe was not bound.");
        }
    }
}